use rocket_db_pools::sqlx::{self, sqlite::SqliteRow, Row};
use std::{
    array::TryFromSliceError,
    net::{IpAddr, Ipv4Addr, Ipv6Addr},
};

pub struct IpBinary(pub [u8; 16]);

static V4_PADDING: [u8; 16] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0, 0, 0, 0];

impl From<IpAddr> for IpBinary {
    fn from(addr: IpAddr) -> Self {
        match addr {
            IpAddr::V4(v4) => {
                let mut array: [u8; 16] = V4_PADDING;
                let v4_octets = v4.octets();
                array[12..16].copy_from_slice(&v4_octets);
                IpBinary(array)
            }
            IpAddr::V6(v6) => IpBinary(v6.octets()),
        }
    }
}

pub enum IpBinaryGetError {
    DataError(TryFromSliceError),
    DatabaseError(sqlx::Error),
}

impl IpBinary {
    pub fn is_v4_mapped(&self) -> bool {
        for (i, item) in self.0.iter().enumerate().take(12) {
            if *item != V4_PADDING[i] {
                return false;
            }
        }
        true
    }

    pub fn try_get_from_row<'r>(
        row: &'r SqliteRow,
        index: &str,
    ) -> Result<IpBinary, IpBinaryGetError> {
        let arr_ref: &'r [u8] = match row.try_get(index) {
            Ok(res) => res,
            Err(err) => return Err(IpBinaryGetError::DatabaseError(err)),
        };
        let arr: [u8; 16] = match arr_ref.try_into() {
            Ok(res) => res,
            Err(err) => return Err(IpBinaryGetError::DataError(err)),
        };
        Ok(IpBinary(arr))
    }
}

impl From<IpBinary> for IpAddr {
    fn from(val: IpBinary) -> Self {
        if val.is_v4_mapped() {
            IpAddr::V4(Ipv4Addr::new(val.0[12], val.0[13], val.0[14], val.0[15]))
        } else {
            IpAddr::V6(Ipv6Addr::from(val.0))
        }
    }
}
